﻿using System;
using System.Collections.Generic;
using System.Linq;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace WorldOnFire
{
    public class CRenderingManager
    {
        #region State Pointers

        private CSkirmishState m_HUDState;

        public void SetHUDState(CSkirmishState _HUDState)
        { m_HUDState = _HUDState; }

        #endregion State Pointers

        #region Models

        // Model IDs
        public enum eModelID
        {
            INVALID = -1,
            SKYBOX,
            RETICLE,
            CELESTIAL_BODY,
            ENEMY_BOMBER,
            ENEMY_DETONATOR,
            ENEMY_GUARDIAN,
            ENEMY_FRIGATE,
            ENEMY_DESTROYER,
            ENEMY_CRUISER,
            ENEMY_BATTLESHIP,
            ENEMY_CARRIER,
            ENEMY_JUGGERNAUT,
            ENEMY_WARP_GATE,
            TURRET_REPEATER,
            TURRET_FLAK,
            TURRET_CANNON,
            TURRET_LASER,
            TURRET_EMP,
            TURRET_GRAVITY_WELL,
            TURRET_BLACK_HOLE,
            TURRET_RELAY,
            PROJECTILE_BULLET,
            PROJECTILE_FLAK,
            PROJECTILE_SHELL,
            PROJECTILE_LASER,
            PROJECTILE_EMP,
            PROJECTILE_BLACK_HOLE,
            PROJECTILE_RELAY,
            PROJECTILE_BOMB,
            PROJECTILE_PLASMA,
            PROJECTILE_MISSILE,
            COUNT
        }

        // Models
        private Model[] m_Models;

        /// <summary>
        /// Gets a Model reference from the manager using an ID.
        /// </summary>
        /// <param name="_eModelID">The ID of the Model to retrieve.</param>
        /// <returns>The requested Model.</returns>
        public Model GetModel(eModelID _eModelID)
        { return m_Models[(int)_eModelID]; }

        /// <summary>
        /// Handles the loading of all Models.
        /// </summary>
        private void LoadModels()
        {
            // Allocate the array
            m_Models = new Model[(int)eModelID.COUNT];

            // Load skybox
            m_Models[(int)eModelID.SKYBOX] = m_ContentManager.Load<Model>("Skybox/Steve the Skybox");

            // Load the HUD
            m_Models[(int)eModelID.RETICLE] = m_ContentManager.Load<Model>("HUD/Reticle");

            // Load celestial bodies
            m_Models[(int)eModelID.CELESTIAL_BODY] = m_ContentManager.Load<Model>("Celestial Bodies/Celestial Body");

            // Load enemies
            m_Models[(int)eModelID.ENEMY_BOMBER] = m_ContentManager.Load<Model>("Enemies/Bomber");
            m_Models[(int)eModelID.ENEMY_DETONATOR] = m_ContentManager.Load<Model>("Enemies/Detonator");
            m_Models[(int)eModelID.ENEMY_GUARDIAN] = m_ContentManager.Load<Model>("Enemies/Guardian");
            m_Models[(int)eModelID.ENEMY_FRIGATE] = m_ContentManager.Load<Model>("Enemies/Frigate");
            m_Models[(int)eModelID.ENEMY_DESTROYER] = m_ContentManager.Load<Model>("Enemies/Destroyer");
            m_Models[(int)eModelID.ENEMY_CRUISER] = m_ContentManager.Load<Model>("Enemies/Cruiser");
            m_Models[(int)eModelID.ENEMY_BATTLESHIP] = m_ContentManager.Load<Model>("Enemies/Battleship");
            m_Models[(int)eModelID.ENEMY_CARRIER] = m_ContentManager.Load<Model>("Enemies/Carrier");
            m_Models[(int)eModelID.ENEMY_JUGGERNAUT] = m_ContentManager.Load<Model>("Enemies/Juggernaut");
            m_Models[(int)eModelID.ENEMY_WARP_GATE] = m_ContentManager.Load<Model>("Enemies/Warp Gate");

            // TODO: Load turrets
            m_Models[(int)eModelID.TURRET_REPEATER] = m_ContentManager.Load<Model>("Turrets/Repeater");
            m_Models[(int)eModelID.TURRET_FLAK] = m_ContentManager.Load<Model>("Turrets/Flak");
            m_Models[(int)eModelID.TURRET_CANNON] = m_ContentManager.Load<Model>("Turrets/Cannon");
            m_Models[(int)eModelID.TURRET_LASER] = m_ContentManager.Load<Model>("Turrets/Laser");
            m_Models[(int)eModelID.TURRET_EMP] = m_ContentManager.Load<Model>("Turrets/EMP");
            m_Models[(int)eModelID.TURRET_GRAVITY_WELL] = m_ContentManager.Load<Model>("Turrets/Gravity Well");
            m_Models[(int)eModelID.TURRET_BLACK_HOLE] = m_ContentManager.Load<Model>("Turrets/Black Hole");
            m_Models[(int)eModelID.TURRET_RELAY] = m_ContentManager.Load<Model>("Turrets/Relay");

            // TODO: Load projectiles
            m_Models[(int)eModelID.PROJECTILE_BULLET] = m_ContentManager.Load<Model>("Projectiles/Bullet");
            //m_Models[(int)eModelID.PROJECTILE_FLAK] = m_ContentManager.Load<Model>("Projectiles/Flak");
            m_Models[(int)eModelID.PROJECTILE_SHELL] = m_ContentManager.Load<Model>("Projectiles/Shell");
            m_Models[(int)eModelID.PROJECTILE_EMP] = m_ContentManager.Load<Model>("Projectiles/EMP");
            //m_Models[(int)eModelID.PROJECTILE_LASER] = m_ContentManager.Load<Model>("Projectiles/Laser");
            //m_Models[(int)eModelID.PROJECTILE_BLACK_HOLE] = m_ContentManager.Load<Model>("Projectiles/Black Hole");
            //m_Models[(int)eModelID.PROJECTILE_RELAY] = m_ContentManager.Load<Model>("Projectiles/Relay");
            //m_Models[(int)eModelID.PROJECTILE_PLASMA] = m_ContentManager.Load<Model>("Projectiles/Plasma");
            m_Models[(int)eModelID.PROJECTILE_MISSILE] = m_ContentManager.Load<Model>("Projectiles/Missile");

        }

        #endregion Models

        #region Textures

        // Texture IDs
        public enum eTextureID
        {
            INVALID = -1,
            SKYBOX_DN,
            SKYBOX_BK,
            SKYBOX_RT,
            SKYBOX_LF,
            SKYBOX_UP,
            SKYBOX_FR,
            HUD_GRID,
            HUD_RETICLE,
            HOMEWORLD_1,
            SATELLITE_1,
            ENEMY,
            ENEMY_ILLUMINATION,
            TURRET,
            TURRET_ILLUMINATION,
            COUNT
        }

        // Textures
        private Texture2D[] m_Textures;

        /// <summary>
        /// Gets a texture reference from the manager using an ID.
        /// </summary>
        /// <param name="_eTextureID">The ID of the texture to retrieve.</param>
        /// <returns>The requested texture.</returns>
        public Texture2D GetTexture(eTextureID _eTextureID)
        { return m_Textures[(int)_eTextureID]; }

        /// <summary>
        /// Handles the loading of all textures.
        /// </summary>
        private void LoadTextures()
        {
            // Allocate the array
            m_Textures = new Texture2D[(int)eTextureID.COUNT];

            // Load skybox textures
            m_Textures[(int)eTextureID.SKYBOX_DN] = m_ContentManager.Load<Texture2D>("Skybox/skybox_DN");
            m_Textures[(int)eTextureID.SKYBOX_BK] = m_ContentManager.Load<Texture2D>("Skybox/skybox_BK");
            m_Textures[(int)eTextureID.SKYBOX_RT] = m_ContentManager.Load<Texture2D>("Skybox/skybox_RT");
            m_Textures[(int)eTextureID.SKYBOX_LF] = m_ContentManager.Load<Texture2D>("Skybox/skybox_LF");
            m_Textures[(int)eTextureID.SKYBOX_UP] = m_ContentManager.Load<Texture2D>("Skybox/skybox_UP");
            m_Textures[(int)eTextureID.SKYBOX_FR] = m_ContentManager.Load<Texture2D>("Skybox/skybox_FR");

            // Load HUD textures
            m_Textures[(int)eTextureID.HUD_GRID] = m_ContentManager.Load<Texture2D>("HUD/Grid");
            m_Textures[(int)eTextureID.HUD_RETICLE] = m_ContentManager.Load<Texture2D>("HUD/ALm01-bump");

            // Load world textures
            m_Textures[(int)eTextureID.HOMEWORLD_1] = m_ContentManager.Load<Texture2D>("Celestial Bodies/Planet");
            m_Textures[(int)eTextureID.SATELLITE_1] = m_ContentManager.Load<Texture2D>("Celestial Bodies/Moon");

            // Load other object textures
            m_Textures[(int)eTextureID.ENEMY] = m_ContentManager.Load<Texture2D>("Enemies/Enemy");
            m_Textures[(int)eTextureID.ENEMY_ILLUMINATION] = m_ContentManager.Load<Texture2D>("Enemies/Enemy Illumination");
            m_Textures[(int)eTextureID.TURRET] = m_ContentManager.Load<Texture2D>("Turrets/Turret");
            m_Textures[(int)eTextureID.TURRET_ILLUMINATION] = m_ContentManager.Load<Texture2D>("Turrets/Turret Illumination");
        }

        #endregion Textures

        #region Effects

        public enum eEffectID
        {
            INVALID = -1,
            OBJECT,
            HUD,
            SKYBOX,
            POST_PROCESSING,
            COUNT
        };

        public enum eObjectTechnique
        {
            INSTANCING,
            ILLUMINATION,
            CELESTIAL_BODY,
            HUD3D,
            DEPTH,
            COUNT
        };

        public enum eHUDTechnique
        {
            DEFAULT,
            DEPTH,
            COUNT
        };

        public enum ePostProcessingTechnique
        {
            DEFAULT,
            OVERLAY,
            BLUR_HORIZONTAL,
            BLUR_VERTICAL,
            BLUR,
            COUNT
        };

        public enum ePostProcessingEffect
        {
            DEFAULT,
            DEFAULT_OVERLAY,
            OVERLAY,
            BLUR,
            COUNT
        };

        private ePostProcessingEffect m_ePostProcessingEffect;

        public ePostProcessingEffect GetPostProcessingEffect()
        { return m_ePostProcessingEffect; }

        public void SetPostProcessingEffect(ePostProcessingEffect _ePostProcessingEffect)
        { m_ePostProcessingEffect = _ePostProcessingEffect; }

        private Effect[] m_Effects;

        /// <summary>
        /// Gets an effect reference using the effect ID.
        /// </summary>
        /// <param name="_eEffectID">The ID of the effect to retrieve.</param>
        /// <returns>The requested effect.</returns>
        public Effect GetEffect(eEffectID _eEffectID)
        { return m_Effects[(int)_eEffectID]; }

        /// <summary>
        /// Handles the loading of all effects.
        /// </summary>
        private void LoadEffects()
        {
            // Initialize the shaders
            m_Effects = new Effect[(int)eEffectID.COUNT];

            // Load the shaders
            m_Effects[(int)eEffectID.OBJECT] = m_ContentManager.Load<Effect>("Shaders/Object");
            m_Effects[(int)eEffectID.HUD] = m_ContentManager.Load<Effect>("Shaders/HUD");
            m_Effects[(int)eEffectID.SKYBOX] = m_ContentManager.Load<Effect>("Shaders/Skybox");
            m_Effects[(int)eEffectID.POST_PROCESSING] = m_ContentManager.Load<Effect>("Shaders/PostProcessing");
        }

        #endregion Effects

        // Content and rendering things
        private ContentManager m_ContentManager;
        private GraphicsDeviceManager m_DeviceManager;
        private GraphicsDevice m_Device;

        // Camera
        private CCamera m_Camera;

        // Skybox
        private CSkybox m_Skybox;

        // Object lists
        private List<ABCObject> m_OpaqueObjectList;
        private List<ABCObject> m_OverlayObjectList;
        //private List<ABCObject> m_TransparentObjectList;

        // Render targets
        RenderTarget2D m_RenderTarget;
        RenderTarget2D m_RenderTargetPost;
        List<RenderTarget2D> m_OverlayRenderTargetList;
        List<Color> m_OverlayColorList;

        private static VertexPositionTexture[] s_vQuadVertices = new VertexPositionTexture[6]
        {
            new VertexPositionTexture(new Vector3(-0.5f, 0.5f, 0.0f), new Vector2(0.0f, 0.0f)),  // TOP LEFT
            new VertexPositionTexture(new Vector3(0.5f, 0.5f, 0.0f), new Vector2(1.0f, 0.0f)),   // TOP RIGHT
            new VertexPositionTexture(new Vector3(-0.5f, -0.5f, 0.0f), new Vector2(0.0f, 1.0f)),   // BOTTOM LEFT
            new VertexPositionTexture(new Vector3(-0.5f, -0.5f, 0.0f), new Vector2(0.0f, 1.0f)),   // BOTTOM LEFT
            new VertexPositionTexture(new Vector3(0.5f, 0.5f, 0.0f), new Vector2(1.0f, 0.0f)),   // TOP RIGHT
            new VertexPositionTexture(new Vector3(0.5f, -0.5f, 0.0f), new Vector2(1.0f, 1.0f))     // BOTTOM RIGHT
        };

        // Graphics
        public const int SCREEN_WIDTH = 1280;
        public const int SCREEN_HEIGHT = 720;

        public CCamera GetCamera()
        { return m_Camera; }

        public GraphicsDevice GetGraphicsDevice()
        { return m_Device; }

        public void AddObject(ABCObject _Object)
        {
            // TODO: Opaque or transparent
            if( _Object.HasTacticalOverlay() == true)
            {
                m_OverlayObjectList.Add(_Object);
            }

            m_OpaqueObjectList.Add(_Object);
        }

        public CRenderingManager()
        {

        }

        public void Initialize(GraphicsDeviceManager _DeviceManager, ContentManager _ContentManager)
        {
            // Store the device, device manager, and content manager
            m_ContentManager = _ContentManager;
            m_DeviceManager = _DeviceManager;
            m_Device = m_DeviceManager.GraphicsDevice;

            // Initialize graphics device
            m_DeviceManager.PreferredBackBufferWidth = SCREEN_WIDTH;
            m_DeviceManager.PreferredBackBufferHeight = SCREEN_HEIGHT;

            // Turn off v-sync
            m_DeviceManager.SynchronizeWithVerticalRetrace = false;
#if !DEBUG
            m_DeviceManager.IsFullScreen = true;
#endif
            m_DeviceManager.ApplyChanges();

            // Load models
            this.LoadModels();

            // Load textures
            this.LoadTextures();

            // Load effects
            this.LoadEffects();

            // Initialize the camera
            m_Camera = new CCamera();
            m_Camera.Initialize();

            m_Camera.SetPerspective(1.25f * MathHelper.PiOver4, m_Device.Viewport.AspectRatio, 1.0f, 2000.0f);
            m_Camera.LookAt(Vector3.Zero, new Vector3(0.0f, 100.0f, 500.0f));

            // Create and initialize the skybox
            m_Skybox = new CSkybox();
            m_Skybox.Initialize(_ContentManager);
            CSkybox.s_RenderingManager = this;

            // Initialize the object lists
            m_OpaqueObjectList = new List<ABCObject>();
            m_OverlayObjectList = new List<ABCObject>();
        
            // Initialize the render targets
            m_RenderTarget = new RenderTarget2D(m_Device, SCREEN_WIDTH, SCREEN_HEIGHT, false, SurfaceFormat.Color, DepthFormat.Depth24);
            m_RenderTargetPost = new RenderTarget2D(m_Device, SCREEN_WIDTH, SCREEN_HEIGHT, false, SurfaceFormat.Color, DepthFormat.Depth24);
            m_Device.SetRenderTarget(m_RenderTarget);
            m_ePostProcessingEffect = ePostProcessingEffect.DEFAULT;

            // Initialize the overlay render targets and colors
            m_OverlayRenderTargetList = new List<RenderTarget2D>();
            m_OverlayColorList = new List<Color>();

            m_OverlayRenderTargetList.Add(new RenderTarget2D(m_Device, SCREEN_WIDTH, SCREEN_HEIGHT));
            m_OverlayRenderTargetList.Add(new RenderTarget2D(m_Device, SCREEN_WIDTH, SCREEN_HEIGHT));
            m_OverlayRenderTargetList.Add(new RenderTarget2D(m_Device, SCREEN_WIDTH, SCREEN_HEIGHT));
            m_OverlayRenderTargetList.Add(new RenderTarget2D(m_Device, SCREEN_WIDTH, SCREEN_HEIGHT));
            m_OverlayRenderTargetList.Add(new RenderTarget2D(m_Device, SCREEN_WIDTH, SCREEN_HEIGHT));
            m_OverlayRenderTargetList.Add(new RenderTarget2D(m_Device, SCREEN_WIDTH, SCREEN_HEIGHT));

            m_OverlayColorList.Add(Color.LimeGreen);
            m_OverlayColorList.Add(Color.Red);
            m_OverlayColorList.Add(Color.Blue);
            m_OverlayColorList.Add(Color.RoyalBlue);
            m_OverlayColorList.Add(Color.Tomato);
            m_OverlayColorList.Add(Color.DarkGray);
        }

        public void Update(float _fDeltaTime)
        {
            // Set the basic effect's matrices
            m_Effects[(int)eEffectID.HUD].Parameters["g_mView"].SetValue(m_Camera.GetView());
            m_Effects[(int)eEffectID.HUD].Parameters["g_mProjection"].SetValue(m_Camera.GetPerspective());

            m_Device.SetRenderTarget(m_RenderTarget);
        }

        public void RenderObjects()
        {

            // Store the camera's matrices
            Matrix mView = m_Camera.GetView();
            Matrix mPerspective = m_Camera.GetPerspective();

            // TODO: Set up lighting
            Vector4[] vLightPositions = new Vector4[]
            {   
                new Vector4(-10000.0f, 0.0f, -500.0f, 1.0f)
                //new Vector4(0.0f, 0.0f, 10000.0f, 1.0f),
                //new Vector4(10000.0f, 0.0f, 0.0f, 1.0f),
                //new Vector4(-2500.0f, -2500.0f, -5000.0f, 1.0f),
                //new Vector4(-2500.0f, 2500.0f, -5000.0f, 1.0f)
            };

            Vector4 vLightAmbient = new Vector4(0.15f, 0.15f, 0.15f, 1.0f);

            Vector4[] vLightDiffuse = new Vector4[]
            {   
                new Vector4(0.8f, 0.5f, 1.0f, 1.0f)
                //new Vector4(0.2f, 0.6f, 0.1f, 1.0f),
                //new Vector4(0.075f, 0.2f, 0.3f, 1.0f),
                //new Vector4(0.2f, 0.2f, 0.0f, 1.0f),
                //new Vector4(0.1f, 0.3f, 0.5f, 1.0f)
            };

            Vector4[] vLightSpecular = new Vector4[]
            {   
                new Vector4(0.3f, 0.2f, 1.0f, 1.0f)
                //new Vector4(0.3f, 0.75f, 0.1f, 1.0f),
                //new Vector4(0.2f, 0.3f, 0.75f, 1.0f),
                //new Vector4(0.5f, 0.5f, 0.1f, 1.0f),
                //new Vector4(0.2f, 0.5f, 0.75f, 1.0f)
            };

            Vector3 vCameraPosition = m_Camera.GetTransform().Translation;

            m_Effects[(int)eEffectID.OBJECT].Parameters["g_vCameraWorldPosition"].SetValue(vCameraPosition);

            m_Effects[(int)eEffectID.OBJECT].Parameters["g_vLightPosition"].SetValue(vLightPositions);
            m_Effects[(int)eEffectID.OBJECT].Parameters["g_vLightAmbient"].SetValue(vLightAmbient);
            m_Effects[(int)eEffectID.OBJECT].Parameters["g_vLightDiffuse"].SetValue(vLightDiffuse);
            m_Effects[(int)eEffectID.OBJECT].Parameters["g_vLightSpecular"].SetValue(vLightSpecular);

            ////////////////////////////////////////////////////////////////////////////////////////
            /* BEGIN: Render skybox ==============================================================*/
            // Remove all translation to properly render the skybox
            Matrix mSkyboxView = mView;
            mSkyboxView.Translation = Vector3.Zero;

            m_Effects[(int)eEffectID.SKYBOX].Parameters["g_mView"].SetValue(mSkyboxView);
            m_Effects[(int)eEffectID.SKYBOX].Parameters["g_mProjection"].SetValue(mPerspective);

            m_Skybox.Render(m_Device);
            /* END: Render skybox ================================================================*/
            ////////////////////////////////////////////////////////////////////////////////////////

            ////////////////////////////////////////////////////////////////////////////////////////
            /* BEGIN: Render opaque ==============================================================*/
            // Set the basic effect's matrices
            m_Effects[(int)eEffectID.OBJECT].Parameters["g_mView"].SetValue(mView);
            m_Effects[(int)eEffectID.OBJECT].Parameters["g_mProjection"].SetValue(mPerspective);

            // TODO: Loop through each opaque object class and add their indices based on distance (nearest to farthest)
            //List<int> OpaqueIndexList = new List<int>();
            //List<int> TransparentIndexList = new List<int>();

            // Render opaque objects
            for (int iObject = 0; iObject < m_OpaqueObjectList.Count; ++iObject)
            //for (int iObject = 0; iObject < OpaqueIndexList.Count; ++iObject)
            {
                m_OpaqueObjectList[iObject].Render(m_Device);
                //m_OpaqueObjectList[OpaqueIndexList[iObject]].Render();
            }
            /* END: Render opaque ================================================================*/
            ////////////////////////////////////////////////////////////////////////////////////////


            ////////////////////////////////////////////////////////////////////////////////////////
            /* BEGIN: Render transparent =========================================================*/
            // Loop through each transparent object class and add their indices based on distance (farthest to nearest)

            // Render transparent objects
            //for (int iObject = 0; iObject < TransparentIndexList.Count; ++iObject)
            {
                //m_TransparentObjectList[OpaqueIndexList[iObject]].Render();
            }
            /* END: Render transparent ===========================================================*/
            ////////////////////////////////////////////////////////////////////////////////////////
        }

        #region Post-Processing

        public void RenderPostProcessing()
        {
            Effect PostProcessEffect = m_Effects[(int)eEffectID.POST_PROCESSING];
            PostProcessEffect.Parameters["g_mWorldViewProjection"]
                .SetValue(Matrix.CreateOrthographic(1.0f, 1.0f, -1.0f, 1.0f));

            switch ((int)m_ePostProcessingEffect)
            {
                case (int)ePostProcessingEffect.DEFAULT:
                    {
                        this.RenderPPDefault();
                    }
                    break;
                case (int)ePostProcessingEffect.DEFAULT_OVERLAY:
                    {
                        this.RenderPPDefaultOverlay();
                    }
                    break;
                case (int)ePostProcessingEffect.OVERLAY:
                    {
                        this.RenderPPOverlay();
                    }
                    break;
                case (int)ePostProcessingEffect.BLUR:
                    {
                        this.RenderPPBlur();
                    }
                    break;
            }
        }

        public void RenderPPDefault()
        {
            // Set and clear the back buffer
            m_Device.SetRenderTarget(null);
            m_Device.Clear(Color.Black);

            // Set the effect technique and parameters
            Effect PostProcessEffect = m_Effects[(int)eEffectID.POST_PROCESSING];
            PostProcessEffect.CurrentTechnique = PostProcessEffect.Techniques[(int)ePostProcessingTechnique.DEFAULT];
            PostProcessEffect.Parameters["g_Texture"].SetValue(m_RenderTarget);

            // Loop through each pass
            foreach (EffectPass Pass in PostProcessEffect.CurrentTechnique.Passes)
            {
                // Begin the pass
                Pass.Apply();

                // Draw magic things
                m_Device.DrawUserPrimitives(PrimitiveType.TriangleList, s_vQuadVertices, 0, 2);
            }
        }

        public void RenderPPDefaultOverlay()
        {
            // Render depth textures
            this.RenderOverlayObjects();

            // Set and clear the back buffer
            m_Device.SetRenderTarget(null);
            m_Device.Clear(Color.Black);

            // Set the effect technique and parameters
            Effect PostProcessEffect = m_Effects[(int)eEffectID.POST_PROCESSING];
            PostProcessEffect.CurrentTechnique = PostProcessEffect.Techniques[(int)ePostProcessingTechnique.DEFAULT];
            PostProcessEffect.Parameters["g_Texture"].SetValue(m_RenderTarget);

            // Loop through each pass
            foreach (EffectPass Pass in PostProcessEffect.CurrentTechnique.Passes)
            {
                // Begin the pass
                Pass.Apply();

                // Draw magic things
                m_Device.DrawUserPrimitives(PrimitiveType.TriangleList, s_vQuadVertices, 0, 2);
            }

            // Set the device state
            m_Device.BlendState = BlendState.AlphaBlend;

            // Set up effect parameters
            PostProcessEffect.CurrentTechnique = PostProcessEffect.Techniques[(int)ePostProcessingTechnique.OVERLAY];
            float fBlurSize = 0.5f / SCREEN_WIDTH;
            PostProcessEffect.Parameters["g_fBlurSize"].SetValue(fBlurSize);

            // Loop through each render target
            for (int iRenderTarget = 0; iRenderTarget < m_OverlayRenderTargetList.Count; ++iRenderTarget)
            {
                // Set effect parameters specific to each render target
                PostProcessEffect.Parameters["g_Texture"].SetValue(m_OverlayRenderTargetList[iRenderTarget]);
                PostProcessEffect.Parameters["g_vOverlayColor"].SetValue(m_OverlayColorList[iRenderTarget].ToVector4());

                // Loop through each pass
                foreach (EffectPass Pass in PostProcessEffect.CurrentTechnique.Passes)
                {
                    // Begin the pass
                    Pass.Apply();

                    // Draw magic things
                    m_Device.DrawUserPrimitives(PrimitiveType.TriangleList, s_vQuadVertices, 0, 2);
                }
            }
        }

        public void RenderPPOverlay()
        {
            // Render depth textures
            this.RenderOverlayObjects();

            // Set and clear the back buffer
            m_Device.SetRenderTarget(null);
            m_Device.Clear(Color.Black);

            // Set the device state
            m_Device.BlendState = BlendState.AlphaBlend;

            // Set up effect parameters
            Effect PostProcessEffect = m_Effects[(int)eEffectID.POST_PROCESSING];
            PostProcessEffect.CurrentTechnique = PostProcessEffect.Techniques[(int)ePostProcessingTechnique.OVERLAY];
            float fBlurSize = 0.5f / SCREEN_WIDTH;
            PostProcessEffect.Parameters["g_fBlurSize"].SetValue(fBlurSize);

            // Loop through each render target
            for (int iRenderTarget = 0; iRenderTarget < m_OverlayRenderTargetList.Count; ++iRenderTarget)
            {
                // Set effect parameters specific to each render target
                PostProcessEffect.Parameters["g_Texture"].SetValue(m_OverlayRenderTargetList[iRenderTarget]);
                PostProcessEffect.Parameters["g_vOverlayColor"].SetValue(m_OverlayColorList[iRenderTarget].ToVector4());

                // Loop through each pass
                foreach (EffectPass Pass in PostProcessEffect.CurrentTechnique.Passes)
                {
                    // Begin the pass
                    Pass.Apply();

                    // Draw magic things
                    m_Device.DrawUserPrimitives(PrimitiveType.TriangleList, s_vQuadVertices, 0, 2);
                }
            }
        }

        public void RenderPPBlur()
        {
            // Set and clear the back buffer
            m_Device.SetRenderTarget(m_RenderTargetPost);
            m_Device.Clear(Color.Black);

            // Set the effect technique and parameters
            Effect PostProcessEffect = m_Effects[(int)eEffectID.POST_PROCESSING];
            PostProcessEffect.CurrentTechnique = PostProcessEffect.Techniques[(int)ePostProcessingTechnique.BLUR_HORIZONTAL];
            PostProcessEffect.Parameters["g_Texture"].SetValue(m_RenderTarget);
            float fBlurSize = 1.5f / SCREEN_WIDTH;
            PostProcessEffect.Parameters["g_fBlurSize"].SetValue(fBlurSize);

            // Loop through each pass
            foreach (EffectPass Pass in PostProcessEffect.CurrentTechnique.Passes)
            {
                // Begin the pass
                Pass.Apply();

                // Draw magic things
                m_Device.DrawUserPrimitives(PrimitiveType.TriangleList, s_vQuadVertices, 0, 2);
            }

            // Set and clear the back buffer
            m_Device.SetRenderTarget(null);
            m_Device.Clear(Color.Black);

            // Set the effect technique and parameters
            PostProcessEffect.CurrentTechnique = PostProcessEffect.Techniques[(int)ePostProcessingTechnique.BLUR_VERTICAL];
            PostProcessEffect.Parameters["g_Texture"].SetValue(m_RenderTargetPost);
            fBlurSize = 1.5f / SCREEN_HEIGHT;
            PostProcessEffect.Parameters["g_fBlurSize"].SetValue(fBlurSize);

            // Loop through each pass
            foreach (EffectPass Pass in PostProcessEffect.CurrentTechnique.Passes)
            {
                // Begin the pass
                Pass.Apply();

                // Draw magic things
                m_Device.DrawUserPrimitives(PrimitiveType.TriangleList, s_vQuadVertices, 0, 2);
            }
        }

        private void RenderOverlayObjects()
        {
            // Render homeworld/satellites
            m_Device.SetRenderTarget(m_OverlayRenderTargetList[0]);
            int iObject = 0;
            int nObjectCount = 2;
            for (; iObject < nObjectCount; ++iObject)
            {
                int nTechnique = m_OverlayObjectList[iObject].GetTechniqueID();
                m_OverlayObjectList[iObject].SetTechniqueID((int)eObjectTechnique.DEPTH);
                m_OverlayObjectList[iObject].Render(m_Device);
                m_OverlayObjectList[iObject].SetTechniqueID(nTechnique);
            }

            // Render enemies and warp gate
            m_Device.SetRenderTarget(m_OverlayRenderTargetList[1]);
            nObjectCount += (int)ABCEnemy.eEnemyType.COUNT + 1;
            for (; iObject < nObjectCount; ++iObject)
            {
                int nTechnique = m_OverlayObjectList[iObject].GetTechniqueID();
                m_OverlayObjectList[iObject].SetTechniqueID((int)eObjectTechnique.DEPTH);
                m_OverlayObjectList[iObject].Render(m_Device);
                m_OverlayObjectList[iObject].SetTechniqueID(nTechnique);
            }

            // Render turrets
            m_Device.SetRenderTarget(m_OverlayRenderTargetList[2]);
            nObjectCount += (int)ABCTurret.eTurretType.COUNT;
            for (; iObject < nObjectCount; ++iObject)
            {
                int nTechnique = m_OverlayObjectList[iObject].GetTechniqueID();
                m_OverlayObjectList[iObject].SetTechniqueID((int)eObjectTechnique.DEPTH);
                m_OverlayObjectList[iObject].Render(m_Device);
                m_OverlayObjectList[iObject].SetTechniqueID(nTechnique);
            }

            // Render turret projectiles
            m_Device.SetRenderTarget(m_OverlayRenderTargetList[3]);
            nObjectCount += (int)ABCProjectile.eProjectileType.COUNT - 3;
            for (; iObject < nObjectCount; ++iObject)
            {
                int nTechnique = m_OverlayObjectList[iObject].GetTechniqueID();
                m_OverlayObjectList[iObject].SetTechniqueID((int)eObjectTechnique.DEPTH);
                m_OverlayObjectList[iObject].Render(m_Device);
                m_OverlayObjectList[iObject].SetTechniqueID(nTechnique);
            }

            // Render enemy projectiles
            m_Device.SetRenderTarget(m_OverlayRenderTargetList[4]);
            nObjectCount += 3;
            for (; iObject < nObjectCount; ++iObject)
            {
                int nTechnique = m_OverlayObjectList[iObject].GetTechniqueID();
                m_OverlayObjectList[iObject].SetTechniqueID((int)eObjectTechnique.DEPTH);
                m_OverlayObjectList[iObject].Render(m_Device);
                m_OverlayObjectList[iObject].SetTechniqueID(nTechnique);
            }

            // Render 3D HUD
            m_Device.SetRenderTarget(m_OverlayRenderTargetList[5]);
            int nHUDTechnique = m_HUDState.GetTechniqueID();
            m_HUDState.SetTechniqueID((int)eHUDTechnique.DEPTH);
            m_HUDState.Render();
            m_HUDState.SetTechniqueID(nHUDTechnique);
        }

        #endregion Post-Processing
    }
}
